Micron Document
baubs git

Node / mirrors / respira / commits / e0fadf6

Commit e0fadf69da980242df26171daccc1e993550e1fb


Parents : acdf87b
Author : Jan-Henrik Bruhn <hi@jhbruhn.de>
Date : 2025-12-05T21:08:52+01:00

Fix PES to PEN conversion and protocol implementation

- Use PyStitch raw stitches with proper command flag handling
- Import constants from pystitch.EmbConstant (STITCH, JUMP, TRIM, etc.)
- Filter COLOR_CHANGE, STOP, and END command-only stitches
- Properly encode jump/trim stitches with PEN_FEED_DATA flag
- Add pattern centering with moveX/moveY in layout
- Fix color change detection and PEN_COLOR_END marking
- Add comprehensive debug logging for pattern analysis
- Fix machine state helpers for IDLE and MASK_TRACE_COMPLETE states
- Update ProgressMonitor UI for proper button visibility
- Add error handling for undefined error codes

Machine now successfully uploads patterns, completes mask trace,
and transitions to sewing mode.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changes
Diff

diff --git a/src/components/ProgressMonitor.tsx b/src/components/ProgressMonitor.tsx
index 67b944a..1effe9d 100644
--- a/src/components/ProgressMonitor.tsx
+++ b/src/components/ProgressMonitor.tsx
@@ -230,15 +230,17 @@ export function ProgressMonitor({
</div>
)}
- {/* Mask trace complete - waiting for confirmation */}
+ {/* Mask trace complete - ready to sew */}
{isMaskTraceComplete && (
<>
<div className="status-message success">
Mask trace complete!
</div>
- <div className="status-message warning">
- Press button on machine to confirm (or trace again)
- </div>
+ {canStartSewing(machineStatus) && (
+ <button onClick={onStartSewing} className="btn-primary">
+ Start Sewing
+ </button>
+ )}
{canStartMaskTrace(machineStatus) && (
<button onClick={onStartMaskTrace} className="btn-secondary">
Trace Again
@@ -247,6 +249,20 @@ export function ProgressMonitor({
</>
)}
+ {/* Pattern uploaded, ready to trace */}
+ {machineStatus === MachineStatus.IDLE && (
+ <>
+ <div className="status-message info">
+ Pattern uploaded successfully
+ </div>
+ {canStartMaskTrace(machineStatus) && (
+ <button onClick={onStartMaskTrace} className="btn-secondary">
+ Start Mask Trace
+ </button>
+ )}
+ </>
+ )}
+
{/* Ready to start (pattern uploaded) */}
{machineStatus === MachineStatus.SEWING_WAIT && (
<>

diff --git a/src/hooks/useBrotherMachine.ts b/src/hooks/useBrotherMachine.ts
index 350a856..f2e57b3 100644
--- a/src/hooks/useBrotherMachine.ts
+++ b/src/hooks/useBrotherMachine.ts
@@ -202,9 +202,13 @@ export function useBrotherMachine() {
try {
setError(null);
setUploadProgress(0);
- const uuid = await service.uploadPattern(penData, (progress) => {
- setUploadProgress(progress);
- });
+ const uuid = await service.uploadPattern(
+ penData,
+ (progress) => {
+ setUploadProgress(progress);
+ },
+ pesData.bounds,
+ );
setUploadProgress(100);
// Cache the pattern with its UUID

diff --git a/src/services/BrotherPP1Service.ts b/src/services/BrotherPP1Service.ts
index 6bfe4ba..21c87c4 100644
--- a/src/services/BrotherPP1Service.ts
+++ b/src/services/BrotherPP1Service.ts
@@ -15,6 +15,7 @@ const Commands = {
MACHINE_INFO: 0x0000,
MACHINE_STATE: 0x0001,
SERVICE_COUNT: 0x0100,
+ REGULAR_INSPECTION: 0x0103,
PATTERN_UUID_REQUEST: 0x0702,
MASK_TRACE: 0x0704,
LAYOUT_SEND: 0x0705,
@@ -25,13 +26,18 @@ const Commands = {
EMB_UUID_SEND: 0x070a,
RESUME_FLAG_REQUEST: 0x070b,
RESUME: 0x070c,
+ HOOP_AVOIDANCE: 0x070f,
START_SEWING: 0x070e,
MASK_TRACE_1: 0x0710,
EMB_ORG_POINT: 0x0800,
+ FIRM_UPDATE_START: 0x0b00,
+ SET_SETTING_REST: 0x0c00,
+ SET_SETTING_SEND: 0x0c01,
MACHINE_SETTING_INFO: 0x0c02,
SEND_DATA_INFO: 0x1200,
SEND_DATA: 0x1201,
CLEAR_ERROR: 0x1300,
+ ERROR_LOG_REPLY: 0x1301,
};
export class BrotherPP1Service {
@@ -132,6 +138,38 @@ export class BrotherPP1Service {
});
}
+ private getCommandName(cmdId: number): string {
+ const names: Record<number, string> = {
+ [Commands.MACHINE_INFO]: "MACHINE_INFO",
+ [Commands.MACHINE_STATE]: "MACHINE_STATE",
+ [Commands.SERVICE_COUNT]: "SERVICE_COUNT",
+ [Commands.REGULAR_INSPECTION]: "REGULAR_INSPECTION",
+ [Commands.PATTERN_UUID_REQUEST]: "PATTERN_UUID_REQUEST",
+ [Commands.MASK_TRACE]: "MASK_TRACE",
+ [Commands.LAYOUT_SEND]: "LAYOUT_SEND",
+ [Commands.EMB_SEWING_INFO_REQUEST]: "EMB_SEWING_INFO_REQUEST",
+ [Commands.PATTERN_SEWING_INFO]: "PATTERN_SEWING_INFO",
+ [Commands.EMB_SEWING_DATA_DELETE]: "EMB_SEWING_DATA_DELETE",
+ [Commands.NEEDLE_MODE_INSTRUCTIONS]: "NEEDLE_MODE_INSTRUCTIONS",
+ [Commands.EMB_UUID_SEND]: "EMB_UUID_SEND",
+ [Commands.RESUME_FLAG_REQUEST]: "RESUME_FLAG_REQUEST",
+ [Commands.RESUME]: "RESUME",
+ [Commands.HOOP_AVOIDANCE]: "HOOP_AVOIDANCE",
+ [Commands.START_SEWING]: "START_SEWING",
+ [Commands.MASK_TRACE_1]: "MASK_TRACE_1",
+ [Commands.EMB_ORG_POINT]: "EMB_ORG_POINT",
+ [Commands.FIRM_UPDATE_START]: "FIRM_UPDATE_START",
+ [Commands.SET_SETTING_REST]: "SET_SETTING_REST",
+ [Commands.SET_SETTING_SEND]: "SET_SETTING_SEND",
+ [Commands.MACHINE_SETTING_INFO]: "MACHINE_SETTING_INFO",
+ [Commands.SEND_DATA_INFO]: "SEND_DATA_INFO",
+ [Commands.SEND_DATA]: "SEND_DATA",
+ [Commands.CLEAR_ERROR]: "CLEAR_ERROR",
+ [Commands.ERROR_LOG_REPLY]: "ERROR_LOG_REPLY",
+ };
+ return names[cmdId] || `UNKNOWN(0x${cmdId.toString(16).padStart(4, "0")})`;
+ }
+
private async sendCommand(
cmdId: number,
data: Uint8Array = new Uint8Array(),
@@ -148,30 +186,41 @@ export class BrotherPP1Service {
command[1] = cmdId & 0xff; // Low byte
command.set(data, 2);
+ const hexData = Array.from(command)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join(" ");
+
console.log(
- "Sending command:",
- Array.from(command)
- .map((b) => b.toString(16).padStart(2, "0"))
- .join(" "),
+ `[TX] ${this.getCommandName(cmdId)} (0x${cmdId.toString(16).padStart(4, "0")}):`,
+ hexData,
);
- console.log("Sending command");
- // Write command and immediately read response
- await this.writeCharacteristic.writeValueWithoutResponse(command);
- console.log("delay");
- // Small delay to ensure response is ready
+ // Write command
+ await this.writeCharacteristic.writeValueWithResponse(command);
+
+ // Longer delay to allow machine to prepare response
await new Promise((resolve) => setTimeout(resolve, 50));
- console.log("reading response");
+ // Read response
const responseData = await this.readCharacteristic.readValue();
const response = new Uint8Array(responseData.buffer);
- console.log(
- "Received response:",
- Array.from(response)
- .map((b) => b.toString(16).padStart(2, "0"))
- .join(" "),
- );
+ const hexResponse = Array.from(response)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join(" ");
+
+ // Parse response
+ let parsed = "";
+ if (response.length >= 3) {
+ const respCmdId = (response[0] << 8) | response[1];
+ const status = response[2];
+ parsed = ` | Status: 0x${status.toString(16).padStart(2, "0")}`;
+ if (respCmdId !== cmdId) {
+ parsed += ` | WARNING: Response cmd 0x${respCmdId.toString(16).padStart(4, "0")} != request cmd`;
+ }
+ }
+
+ console.log(`[RX] ${this.getCommandName(cmdId)}:`, hexResponse, parsed);
return response;
});
@@ -289,7 +338,7 @@ export class BrotherPP1Service {
}
}
- async sendDataChunk(offset: number, data: Uint8Array): Promise<boolean> {
+ async sendDataChunk(offset: number, data: Uint8Array): Promise<void> {
const checksum = data.reduce((sum, byte) => (sum + byte) & 0xff, 0);
const payload = new Uint8Array(4 + data.length + 1);
@@ -303,10 +352,81 @@ export class BrotherPP1Service {
payload.set(data, 4);
payload[4 + data.length] = checksum;
- const response = await this.sendCommand(Commands.SEND_DATA, payload);
+ // Official app approach: Send chunk without waiting for response
+ await this.sendCommandNoResponse(Commands.SEND_DATA, payload);
+ }
+
+ private async sendCommandNoResponse(
+ cmdId: number,
+ data: Uint8Array = new Uint8Array(),
+ ): Promise<void> {
+ return this.enqueueCommand(async () => {
+ if (!this.writeCharacteristic) {
+ throw new Error("Not connected");
+ }
+
+ // Build command with big-endian command ID
+ const command = new Uint8Array(2 + data.length);
+ command[0] = (cmdId >> 8) & 0xff; // High byte
+ command[1] = cmdId & 0xff; // Low byte
+ command.set(data, 2);
+
+ const hexData = Array.from(command)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join(" ");
+
+ console.log(
+ `[TX-NoResp] ${this.getCommandName(cmdId)} (0x${cmdId.toString(16).padStart(4, "0")}):`,
+ hexData.substring(0, 100) + (hexData.length > 100 ? "..." : ""),
+ );
- // 0x00 = complete, 0x02 = continue
- return response[2] === 0x00;
+ // Write without reading response
+ await this.writeCharacteristic.writeValueWithResponse(command);
+
+ // Small delay to allow BLE buffer to clear
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ });
+ }
+
+ private async pollForTransferComplete(): Promise<void> {
+ if (!this.readCharacteristic) {
+ throw new Error("Not connected");
+ }
+
+ // Poll until transfer is complete
+ while (true) {
+ const responseData = await this.readCharacteristic.readValue();
+ const response = new Uint8Array(responseData.buffer);
+
+ console.log(
+ "Poll response:",
+ Array.from(response)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join(" "),
+ );
+
+ // Check response format: [CMD_HIGH, CMD_LOW, STATUS]
+ if (response.length < 3) {
+ throw new Error("Invalid response length");
+ }
+
+ const status = response[2];
+
+ if (status === 0x01) {
+ // Error
+ throw new Error("Transfer failed");
+ } else if (status === 0x00) {
+ // Complete
+ console.log("Transfer complete");
+ break;
+ } else if (status === 0x02) {
+ // Continue - wait 1 second and poll again (as per official app)
+ console.log("Transfer in progress, waiting...");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ } else {
+ throw new Error(`Unknown transfer status: 0x${status.toString(16)}`);
+ }
+ }
}
async sendUUID(uuid: Uint8Array): Promise<void> {
@@ -325,14 +445,19 @@ export class BrotherPP1Service {
rotate: number,
flip: number,
frame: number,
+ boundLeft: number,
+ boundTop: number,
+ boundRight: number,
+ boundBottom: number,
): Promise<void> {
- const payload = new Uint8Array(12);
+ const payload = new Uint8Array(26);
const writeInt16LE = (offset: number, value: number) => {
payload[offset] = value & 0xff;
payload[offset + 1] = (value >> 8) & 0xff;
};
+ // Position/transformation parameters (12 bytes)
writeInt16LE(0, moveX);
writeInt16LE(2, moveY);
writeInt16LE(4, sizeX);
@@ -341,11 +466,41 @@ export class BrotherPP1Service {
payload[10] = flip;
payload[11] = frame;
+ // Pattern bounds (8 bytes)
+ writeInt16LE(12, boundLeft);
+ writeInt16LE(14, boundTop);
+ writeInt16LE(16, boundRight);
+ writeInt16LE(18, boundBottom);
+
+ // Repeat moveX and moveY at the end (6 bytes)
+ writeInt16LE(20, moveX);
+ writeInt16LE(22, moveY);
+ payload[24] = flip;
+ payload[25] = frame;
+
+ console.log('[DEBUG] Layout bounds:', {
+ boundLeft,
+ boundTop,
+ boundRight,
+ boundBottom,
+ moveX,
+ moveY,
+ sizeX,
+ sizeY,
+ });
+
await this.sendCommand(Commands.LAYOUT_SEND, payload);
}
+ async getMachineSettings(): Promise<Uint8Array> {
+ return await this.sendCommand(Commands.MACHINE_SETTING_INFO);
+ }
+
async startMaskTrace(): Promise<void> {
- const payload = new Uint8Array([0x01]);
+ // Query machine settings before starting mask trace (as per official app)
+ await this.getMachineSettings();
+
+ const payload = new Uint8Array([0x00]);
await this.sendCommand(Commands.MASK_TRACE, payload);
}
@@ -362,6 +517,7 @@ export class BrotherPP1Service {
async uploadPattern(
data: Uint8Array,
onProgress?: (progress: number) => void,
+ bounds?: { minX: number; maxX: number; minY: number; maxY: number },
): Promise<Uint8Array> {
// Calculate checksum
const checksum = data.reduce((sum, byte) => sum + byte, 0) & 0xffff;
@@ -376,34 +532,64 @@ export class BrotherPP1Service {
const chunkSize = 500;
let offset = 0;
+ // Send all chunks without waiting for responses (official app approach)
while (offset < data.length) {
const chunk = data.slice(
offset,
Math.min(offset + chunkSize, data.length),
);
- const isComplete = await this.sendDataChunk(offset, chunk);
+ await this.sendDataChunk(offset, chunk);
offset += chunk.length;
if (onProgress) {
onProgress((offset / data.length) * 100);
}
-
- if (isComplete) {
- break;
- }
-
- // Small delay between chunks
- await new Promise((resolve) => setTimeout(resolve, 10));
}
+ // Wait a bit for machine to finish processing chunks
+ await new Promise((resolve) => setTimeout(resolve, 500));
+
+ // Use provided bounds or default to 0
+ const boundLeft = bounds?.minX ?? 0;
+ const boundTop = bounds?.minY ?? 0;
+ const boundRight = bounds?.maxX ?? 0;
+ const boundBottom = bounds?.maxY ?? 0;
+
+ // Calculate pattern dimensions
+ const patternWidth = boundRight - boundLeft;
+ const patternHeight = boundBottom - boundTop;
+
+ // Calculate center offset to position pattern at machine center
+ // Machine embroidery area center is at (0, 0)
+ // Pattern center should align with machine center
+ const patternCenterX = (boundLeft + boundRight) / 2;
+ const patternCenterY = (boundTop + boundBottom) / 2;
+
+ // moveX/moveY shift the pattern so its center aligns with origin
+ const moveX = -patternCenterX;
+ const moveY = -patternCenterY;
+
+ // Send layout with actual pattern bounds
+ // sizeX/sizeY are scaling factors (100 = 100% = no scaling)
+ await this.sendLayout(
+ Math.round(moveX), // moveX - center the pattern
+ Math.round(moveY), // moveY - center the pattern
+ 100, // sizeX (100% - no scaling)
+ 100, // sizeY (100% - no scaling)
+ 0, // rotate
+ 0, // flip
+ 1, // frame
+ boundLeft,
+ boundTop,
+ boundRight,
+ boundBottom,
+ );
+
// Generate random UUID
const uuid = crypto.getRandomValues(new Uint8Array(16));
await this.sendUUID(uuid);
- // Send default layout (no transformation)
- await this.sendLayout(0, 0, 0, 0, 0, 0, 0);
-
console.log(
"Pattern uploaded successfully with UUID:",
Array.from(uuid)

diff --git a/src/utils/errorCodeHelpers.ts b/src/utils/errorCodeHelpers.ts
index 989be88..5a734ea 100644
--- a/src/utils/errorCodeHelpers.ts
+++ b/src/utils/errorCodeHelpers.ts
@@ -76,7 +76,12 @@ const ERROR_MESSAGES: Record<number, string> = {
/**
* Get human-readable error message for an error code
*/
-export function getErrorMessage(errorCode: number): string | null {
+export function getErrorMessage(errorCode: number | undefined): string | null {
+ // Handle undefined or null
+ if (errorCode === undefined || errorCode === null) {
+ return null;
+ }
+
// 0xDD (221) is the default "no error" value
if (errorCode === SewingMachineError.None) {
return null; // No error to display
@@ -95,6 +100,6 @@ export function getErrorMessage(errorCode: number): string | null {
/**
* Check if error code represents an actual error condition
*/
-export function hasError(errorCode: number): boolean {
- return errorCode !== SewingMachineError.None;
+export function hasError(errorCode: number | undefined): boolean {
+ return errorCode !== undefined && errorCode !== null && errorCode !== SewingMachineError.None;
}

diff --git a/src/utils/machineStateHelpers.ts b/src/utils/machineStateHelpers.ts
index b1e8fc2..a1897a9 100644
--- a/src/utils/machineStateHelpers.ts
+++ b/src/utils/machineStateHelpers.ts
@@ -88,6 +88,7 @@ export function canUploadPattern(status: MachineStatus): boolean {
export function canStartSewing(status: MachineStatus): boolean {
// Only in specific ready states
return status === MachineStatus.SEWING_WAIT ||
+ status === MachineStatus.MASK_TRACE_COMPLETE ||
status === MachineStatus.PAUSE ||
status === MachineStatus.STOP ||
status === MachineStatus.SEWING_INTERRUPTION;
@@ -97,8 +98,9 @@ export function canStartSewing(status: MachineStatus): boolean {
* Determines if mask trace can be started in the current state.
*/
export function canStartMaskTrace(status: MachineStatus): boolean {
- // Only when ready or after previous trace
- return status === MachineStatus.SEWING_WAIT ||
+ // Can start mask trace when IDLE (after upload), SEWING_WAIT, or after previous trace
+ return status === MachineStatus.IDLE ||
+ status === MachineStatus.SEWING_WAIT ||
status === MachineStatus.MASK_TRACE_COMPLETE;
}

diff --git a/src/utils/pystitchConverter.ts b/src/utils/pystitchConverter.ts
index 4c31af8..3c07be3 100644
--- a/src/utils/pystitchConverter.ts
+++ b/src/utils/pystitchConverter.ts
@@ -46,45 +46,38 @@ export async function convertPesToPen(file: File): Promise<PesPatternData> {
// Read the pattern using PyStitch
const result = await pyodide.runPythonAsync(`
import pystitch
+from pystitch.EmbConstant import STITCH, JUMP, TRIM, STOP, END, COLOR_CHANGE
# Read the PES file
pattern = pystitch.read('${filename}')
-# PyStitch groups stitches by color blocks using get_as_stitchblock
-# This returns tuples of (thread, stitches_list) for each color block
+# Use the raw stitches list which preserves command flags
+# Each stitch in pattern.stitches is [x, y, cmd]
+# We need to assign color indices based on COLOR_CHANGE commands
+# and filter out COLOR_CHANGE and STOP commands (they're not actual stitches)
+
stitches_with_colors = []
-block_index = 0
-
-# Iterate through stitch blocks
-# Each block is a tuple containing (thread, stitch_list)
-for block in pattern.get_as_stitchblock():
- if isinstance(block, tuple):
- # Extract thread and stitch list from tuple
- thread_obj = None
- stitches_list = None
-
- for elem in block:
- # Check if this is the thread object (has color or hex_color attributes)
- if hasattr(elem, 'color') or hasattr(elem, 'hex_color'):
- thread_obj = elem
- # Check if this is the stitch list
- elif isinstance(elem, list) and len(elem) > 0 and isinstance(elem[0], list):
- stitches_list = elem
-
- if stitches_list:
- # Find the index of this thread in the threadlist
- thread_index = block_index
- if thread_obj and hasattr(pattern, 'threadlist'):
- for i, t in enumerate(pattern.threadlist):
- if t is thread_obj:
- thread_index = i
- break
-
- for stitch in stitches_list:
- # stitch is [x, y, command]
- stitches_with_colors.append([stitch[0], stitch[1], stitch[2], thread_index])
-
- block_index += 1
+current_color = 0
+
+for i, stitch in enumerate(pattern.stitches):
+ x, y, cmd = stitch
+
+ # Check for color change command - increment color but don't add stitch
+ if cmd == COLOR_CHANGE:
+ current_color += 1
+ continue
+
+ # Check for stop command - skip it
+ if cmd == STOP:
+ continue
+
+ # Check for standalone END command (no stitch data)
+ if cmd == END:
+ continue
+
+ # Add actual stitch with color index and command
+ # Keep JUMP/TRIM flags as they indicate jump stitches
+ stitches_with_colors.append([x, y, cmd, current_color])
# Convert to JSON-serializable format
{
@@ -98,13 +91,16 @@ for block in pattern.get_as_stitchblock():
],
'thread_count': len(pattern.threadlist),
'stitch_count': len(stitches_with_colors),
- 'block_count': block_index
+ 'color_changes': current_color
}
`);
// Convert Python result to JavaScript
const data = result.toJs({ dict_converter: Object.fromEntries });
+ console.log('[DEBUG] PyStitch stitch_count:', data.stitch_count);
+ console.log('[DEBUG] PyStitch color_changes:', data.color_changes);
+
// Clean up virtual file
try {
pyodide.FS.unlink(filename);
@@ -117,6 +113,32 @@ for block in pattern.get_as_stitchblock():
Array.from(stitch) as number[]
);
+ console.log('[DEBUG] JavaScript stitches.length:', stitches.length);
+ console.log('[DEBUG] First 5 stitches:', stitches.slice(0, 5));
+ console.log('[DEBUG] Middle 5 stitches:', stitches.slice(Math.floor(stitches.length / 2), Math.floor(stitches.length / 2) + 5));
+ console.log('[DEBUG] Last 5 stitches:', stitches.slice(-5));
+
+ // Count stitch types (PyStitch constants: STITCH=0, JUMP=1, TRIM=2)
+ let jumpCount = 0, normalCount = 0;
+ for (let i = 0; i < stitches.length; i++) {
+ const cmd = stitches[i][2];
+ if (cmd === 1 || cmd === 2) jumpCount++; // JUMP or TRIM
+ else normalCount++; // STITCH (0)
+ }
+ console.log('[DEBUG] Stitch types: normal=' + normalCount + ', jump/trim=' + jumpCount);
+
+ // Calculate min/max of raw stitch values to understand the data
+ let rawMinX = Infinity, rawMaxX = -Infinity, rawMinY = Infinity, rawMaxY = -Infinity;
+ for (let i = 0; i < stitches.length; i++) {
+ const x = stitches[i][0];
+ const y = stitches[i][1];
+ rawMinX = Math.min(rawMinX, x);
+ rawMaxX = Math.max(rawMaxX, x);
+ rawMinY = Math.min(rawMinY, y);
+ rawMaxY = Math.max(rawMaxY, y);
+ }
+ console.log('[DEBUG] Raw stitch value ranges:', { rawMinX, rawMaxX, rawMinY, rawMaxY });
+
if (!stitches || stitches.length === 0) {
throw new Error('Invalid PES file or no stitches found');
}
@@ -133,32 +155,35 @@ for block in pattern.get_as_stitchblock():
let minY = Infinity;
let maxY = -Infinity;
- // Convert to PEN format
+ // PyStitch returns ABSOLUTE coordinates
+ // PEN format uses absolute coordinates, shifted left by 3 bits (as per official app line 780)
const penStitches: number[] = [];
let currentColor = stitches[0]?.[3] ?? 0; // Track current color using stitch color index
for (let i = 0; i < stitches.length; i++) {
const stitch = stitches[i];
- const x = Math.round(stitch[0]);
- const y = Math.round(stitch[1]);
+ const absX = Math.round(stitch[0]);
+ const absY = Math.round(stitch[1]);
const cmd = stitch[2];
const stitchColor = stitch[3]; // Color index from PyStitch
- // Track bounds for non-jump stitches
- if ((cmd & MOVE) === 0) {
- minX = Math.min(minX, x);
- maxX = Math.max(maxX, x);
- minY = Math.min(minY, y);
- maxY = Math.max(maxY, y);
+ // Track bounds for non-jump stitches (cmd=0 is STITCH)
+ if (cmd === 0) {
+ minX = Math.min(minX, absX);
+ maxX = Math.max(maxX, absX);
+ minY = Math.min(minY, absY);
+ maxY = Math.max(maxY, absY);
}
- // Encode coordinates with flags in low 3 bits
+ // Encode absolute coordinates with flags in low 3 bits
// Shift coordinates left by 3 bits to make room for flags
- let xEncoded = (x << 3) & 0xFFFF;
- let yEncoded = (y << 3) & 0xFFFF;
+ // As per official app line 780: buffer[index64] = (byte) ((int) numArray4[index64 / 4, 0] << 3 & (int) byte.MaxValue);
+ let xEncoded = (absX << 3) & 0xFFFF;
+ let yEncoded = (absY << 3) & 0xFFFF;
- // Add jump flag if this is a move command
- if ((cmd & MOVE) !== 0) {
+ // Add jump flag if this is a JUMP (1) or TRIM (2) command
+ // PyStitch constants: STITCH=0, JUMP=1, TRIM=2
+ if (cmd === 1 || cmd === 2) {
yEncoded |= PEN_FEED_DATA;
}
@@ -200,6 +225,35 @@ for block in pattern.get_as_stitchblock():
const penData = new Uint8Array(penStitches);
+ console.log('[DEBUG] PEN data size:', penData.length, 'bytes');
+ console.log('[DEBUG] Encoded stitch count:', penData.length / 4);
+ console.log('[DEBUG] Expected vs Actual:', data.stitch_count, 'vs', penData.length / 4);
+ console.log('[DEBUG] First 20 bytes (5 stitches):',
+ Array.from(penData.slice(0, 20))
+ .map(b => b.toString(16).padStart(2, '0'))
+ .join(' '));
+ console.log('[DEBUG] Last 20 bytes (5 stitches):',
+ Array.from(penData.slice(-20))
+ .map(b => b.toString(16).padStart(2, '0'))
+ .join(' '));
+ console.log('[DEBUG] Calculated bounds from stitches:', {
+ minX,
+ maxX,
+ minY,
+ maxY,
+ });
+
+ // Check for color change markers and end marker
+ let colorChangeCount = 0;
+ let hasEndMarker = false;
+ for (let i = 0; i < penData.length; i += 4) {
+ const xLow = penData[i];
+ const yLow = penData[i + 2];
+ if ((xLow & 0x07) === PEN_COLOR_END) colorChangeCount++;
+ if ((xLow & 0x07) === PEN_DATA_END) hasEndMarker = true;
+ }
+ console.log('[DEBUG] Color changes found:', colorChangeCount, '| Has END marker:', hasEndMarker);
+
return {
stitches,
threads,

Served by rngit 1.4.2 - Generated in 0.09s